﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using System.Xml;
using System.Xml.Linq;

namespace InkSpot
{
    /// <summary>
    /// Interaction logic for SVG.xaml
    /// </summary>
    public partial class SVG : UserControl
    {

        #region Property:Source
        /// <summary>
        /// Gets or sets the Source assigned to the control.
        /// </summary>
        public Uri Source
        {
            get { return (Uri)GetValue(SourceProperty); }
            set
            {
                SetValue(SourceProperty, value);
            }
        }

        /// <summary>
        /// Identifies the Source dependency property.
        /// </summary>
        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register(
                "Source", typeof(Uri), typeof(SVG),
                new FrameworkPropertyMetadata((Uri)null, new PropertyChangedCallback(OnSourceChanged)));

        private static void OnSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            SVG control = (SVG)obj;

            RoutedPropertyChangedEventArgs<Uri> e = new RoutedPropertyChangedEventArgs<Uri>(
                (Uri)args.OldValue, (Uri)args.NewValue, SourceChangedEvent);
            control.OnSourceChanged(e);
        }

        /// <summary>
        /// Identifies the SourceChanged routed event.
        /// </summary>
        public static readonly RoutedEvent SourceChangedEvent = EventManager.RegisterRoutedEvent(
            "SourceChanged", RoutingStrategy.Bubble,
            typeof(RoutedPropertyChangedEventHandler<Uri>), typeof(SVG));

        /// <summary>
        /// Occurs when the Source property changes.
        /// </summary>
        public event RoutedPropertyChangedEventHandler<Uri> SourceChanged
        {
            add { AddHandler(SourceChangedEvent, value); }
            remove { RemoveHandler(SourceChangedEvent, value); }
        }

        /// <summary>
        /// Raises the SourceChanged event.
        /// </summary>
        /// <param name="args">Arguments associated with the SourceChanged event.</param>
        protected virtual void OnSourceChanged(RoutedPropertyChangedEventArgs<Uri> args)
        {
            _OnSourceChanged();
            RaiseEvent(args);
        }
        #endregion

        #region Property:Stretch
        /// <summary>
        /// Gets or sets the Stretch assigned to the control.
        /// </summary>
        public Stretch Stretch
        {
            get { return (Stretch)GetValue(StretchProperty); }
            set
            {
                SetValue(StretchProperty, value);
            }
        }

        /// <summary>
        /// Identifies the Stretch dependency property.
        /// </summary>
        public static readonly DependencyProperty StretchProperty =
            DependencyProperty.Register(
                "Stretch", typeof(Stretch), typeof(SVG),
                new FrameworkPropertyMetadata((Stretch)Stretch.None, new PropertyChangedCallback(OnStretchChanged)));

        private static void OnStretchChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            SVG control = (SVG)obj;

            RoutedPropertyChangedEventArgs<Stretch> e = new RoutedPropertyChangedEventArgs<Stretch>(
                (Stretch)args.OldValue, (Stretch)args.NewValue, StretchChangedEvent);
            control.OnStretchChanged(e);
        }

        /// <summary>
        /// Identifies the StretchChanged routed event.
        /// </summary>
        public static readonly RoutedEvent StretchChangedEvent = EventManager.RegisterRoutedEvent(
            "StretchChanged", RoutingStrategy.Bubble,
            typeof(RoutedPropertyChangedEventHandler<Stretch>), typeof(SVG));

        /// <summary>
        /// Occurs when the Stretch property changes.
        /// </summary>
        public event RoutedPropertyChangedEventHandler<Stretch> StretchChanged
        {
            add { AddHandler(StretchChangedEvent, value); }
            remove { RemoveHandler(StretchChangedEvent, value); }
        }

        /// <summary>
        /// Raises the StretchChanged event.
        /// </summary>
        /// <param name="args">Arguments associated with the StretchChanged event.</param>
        protected virtual void OnStretchChanged(RoutedPropertyChangedEventArgs<Stretch> args)
        {
            _OnStretchChanged();
            RaiseEvent(args);
        }
        #endregion
        
        public SVG()
        {
            InitializeComponent();
            this.SizeChanged += new SizeChangedEventHandler(SVG_SizeChanged);
        }

        void SVG_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            _OnStretchChanged();
        }
        
        private void _OnSourceChanged()
        {
            Stream str = null;
            MainGrid.Children.Clear();
            
            if (Source.IsAbsoluteUri)
            {
                if (Source.IsFile)
                    str = File.OpenRead(Source.AbsolutePath);
            }
            else
                str = Application.GetResourceStream(Source).Stream;

            XmlDocument svg_doc = new XmlDocument();
            svg_doc.Load(str);
            XmlElement svg_main = svg_doc.DocumentElement;
            string 
                txt_svg_width = svg_main.GetAttribute("width"),
                txt_svg_height = svg_main.GetAttribute("height"),
                txt_svg_x = svg_main.GetAttribute("x"),
                txt_svg_y = svg_main.GetAttribute("y");
            double 
                svg_width = double.Parse(txt_svg_width.Substring(0, txt_svg_width.Length -2)),
                svg_height = double.Parse(txt_svg_height.Substring(0, txt_svg_height.Length - 2)),
                svg_x = (txt_svg_x.Length > 0) ? double.Parse(txt_svg_x.Substring(0, txt_svg_x.Length - 2)) : 0D,
                svg_y = (txt_svg_y.Length > 0) ? double.Parse(txt_svg_y.Substring(0, txt_svg_y.Length - 2)) : 0D;
            // Points Conversion
            if (txt_svg_width.IndexOf("pt") > -1) svg_width *= 5D / 4D;
            if (txt_svg_height.IndexOf("pt") > -1) svg_height *= 5D / 4D;
            if (txt_svg_x.IndexOf("pt") > -1) svg_x *= 5D / 4D;
            if (txt_svg_y.IndexOf("pt") > -1) svg_y *= 5D / 4D;
            // --
            MainGrid.Width = svg_width + svg_x;
            MainGrid.Height = svg_height + svg_y;
            List<Shape> shapes = new List<Shape>();
            ParseSVG(MainGrid, svg_main, svg_width, svg_height, svg_x, svg_y, shapes);
            After_OnSourceChanged(shapes);
            shapes.Clear();
            _OnStretchChanged();
        }

        private void _OnStretchChanged()
        {
            double scale_x = 1, scale_y = 1;
            MainGrid.RenderTransform = null;
            MainGrid.LayoutTransform = null;
            switch (this.Stretch)
            {
                case System.Windows.Media.Stretch.Fill:
                    scale_x = ActualWidth / MainGrid.Width;
                    scale_y = ActualHeight / MainGrid.Height;
                    break;
                case System.Windows.Media.Stretch.Uniform:
                    scale_x = ActualWidth / MainGrid.Width;
                    if ((MainGrid.Height * scale_x) > ActualHeight) scale_x = ActualHeight / MainGrid.Height;
                    scale_y = scale_x;
                    break;
                case System.Windows.Media.Stretch.UniformToFill:
                    scale_x = (ActualWidth > ActualHeight) ? ActualWidth / MainGrid.Width : ActualHeight / MainGrid.Height;
                    scale_y = scale_x;
                    break;
            }
            Transform t = new ScaleTransform(scale_x, scale_y, MainGrid.Width / 2, MainGrid.Height / 2);
            if ((scale_x < 1) || (scale_y < 1))
                MainGrid.LayoutTransform = t;
            else
                MainGrid.RenderTransform = t;
        }

        protected virtual void After_OnSourceChanged(List<Shape> shapes) { }

        #region Parsing
        public static void ParseSVG(Panel parent, XmlElement svg_main, double svg_width, double svg_height, double x, double y, List<Shape> shapes)
        {
            foreach (XmlNode g in svg_main.ChildNodes)
            {
                if (g.Name == "g") ParseCanvas(parent, svg_width, svg_height, x, y, shapes, g);
            }
        }

        public static void ParseCanvas(Panel parent, double svg_width, double svg_height, double x, double y, List<Shape> shapes, XmlNode g)
        {
            Canvas g_canvas = new Canvas();
            g_canvas.Width = svg_width;
            g_canvas.Height = svg_height;
            g_canvas.Margin = new Thickness(x, y, 0D, 0D);
            g_canvas.Name = (g as XmlElement).GetAttribute("id");
            Grid.SetRow(g_canvas, 0);
            Grid.SetColumn(g_canvas, 0);
            parent.Children.Add(g_canvas);
            Shape g_shape;
            foreach (XmlNode GeometryXml in g.ChildNodes)
            {
                g_shape = ShapeSVG(GeometryXml);
                if (g_shape == null)
                {
                    if (GeometryXml.Name == "g") ParseCanvas(g_canvas, svg_width, svg_height, 0, 0, shapes, GeometryXml);
                    continue;
                }
                g_canvas.Children.Add(g_shape);
                if (shapes != null) shapes.Add(g_shape);
            }
        }

        public static Shape ShapeSVG(XmlNode GeometryXml)
        {
            Shape g_shape;
            g_shape = null;
            switch (GeometryXml.Name)
            {
                case "polygon":
                    g_shape = CreatePolygon(GeometryXml as XmlElement);
                    break;
                case "path":
                    g_shape = CreatePath(GeometryXml as XmlElement);
                    break;
                case "rect":
                    g_shape = CreateRect(GeometryXml as XmlElement);
                    break;
                case "circle":
                    g_shape = CreateCircle(GeometryXml as XmlElement);
                    break;
            }
            if (g_shape == null) return null;

            AddProperties(GeometryXml as XmlElement, g_shape);
            return g_shape;
        }

        private static void AddProperties(XmlElement GeometryXml, Shape g_shape)
        {
            string trans = GeometryXml.GetAttribute("transform");
            if (trans != "")
            {
                if (trans.IndexOf("matrix") == 0) g_shape.RenderTransform =
                     Transform.Parse(trans.Substring(7, trans.Length - 8).Replace(' ', ','));
            };
            string fill = GeometryXml.GetAttribute("fill");
            if (fill == "none")
            {
                g_shape.Fill = Brushes.Transparent;
                g_shape.Stroke = Brushes.Transparent;
            }
        }
        #endregion

        #region TheShapes
        private static Polygon CreatePolygon(XmlElement GeometryXml)
        {
            Polygon polygon = new Polygon();
            polygon.Points = PointCollection.Parse(GeometryXml.GetAttribute("points"));
            return polygon;
        }

        private static System.Windows.Shapes.Path CreatePath(XmlElement GeometryXml)
        {
            System.Windows.Shapes.Path path = new System.Windows.Shapes.Path();
            path.Data = Geometry.Parse(GeometryXml.GetAttribute("d"));
            return path;
        }

        private static System.Windows.Shapes.Path CreateRect(XmlElement GeometryXml)
        {
            System.Windows.Shapes.Path path = new System.Windows.Shapes.Path();
            string x = GeometryXml.GetAttribute("x");
            string y = GeometryXml.GetAttribute("y");
            path.Data = new RectangleGeometry(
                new System.Windows.Rect(
                    (x == "") ? 0D : double.Parse(x),
                    (y == "") ? 0D : double.Parse(y),
                    double.Parse(GeometryXml.GetAttribute("width")),
                    double.Parse(GeometryXml.GetAttribute("height"))));
            return path;
        }

        private static System.Windows.Shapes.Path CreateCircle(XmlElement GeometryXml)
        {
            System.Windows.Shapes.Path path = new System.Windows.Shapes.Path();
            string cx = GeometryXml.GetAttribute("cx");
            string cy = GeometryXml.GetAttribute("cy");
            double r = double.Parse(GeometryXml.GetAttribute("r"));
            path.Data = new EllipseGeometry(
                new System.Windows.Point(
                    (cx == "") ? 0D : double.Parse(cx),
                    (cy == "") ? 0D : double.Parse(cy)),
                    r, r);
            return path;
        }
        #endregion
    }
}
